/***************************************************************************
 *   Copyright (C) 2008 by Eduardo Gurgel Pinho                            *
 *   edgurgel@gmail.com                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/

#include "window.h"

#include "main_widget.h"
#include "scene_tree.h"
#include "object_panel.h"
#include "material_panel.h"
#include "scene_panel.h"
#include "raytracer_panel.h"
#include "camera_panel.h"
#include "render_thread.h"
#include "image_widget.h"
#include "qtimage.h" 

#include "common/scene.h"
#include "raytracing/raytracer.h"
#include "common/grid.h"
#include "common/image.h"

// #include <iostream>

#include <QDockWidget>
#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QToolBar>
#include <QFile>
#include <QString>
#include <QTime>
#include <QTimer>
#include <QPushButton>
#include <QProgressBar>
#include <QFileDialog>
#include <QMessageBox>

#include <boost/tuple/tuple.hpp>

using namespace boost::tuples;

namespace Ui {

Window::Window(void)  : file(false) , raytracer(0), camera(0) , scene(0) , raytracer_rendering(0), progress_timer(0), render_thread(0) {
	statusbar = new QStatusBar(this);
	setStatusBar(statusbar);
	main_widget = new Main_Widget(this);
	create_toolbar();
	create_dock_widgets();
	create_menu();
	create_connections();
	init();
}

Window::~Window(void) {
	delete main_widget;
	delete scene_tree;
	delete raytracer_panel;
	delete object_panel;
	delete material_panel;
	delete scene_panel;
	delete camera_panel;
	if(raytracer != 0) delete raytracer;
}

void Window::new_file(void) {
	QString filename = QFileDialog::getSaveFileName(this,tr("New File"), QDir::currentPath(),tr("Sirat Files (*.sirat)"));
	if (!filename.isNull()) {
		file = true;
		current_filename = filename;
		scene_tree->clear();
		material_panel->clear();
		emit set_clear();
		raytracer = new Sirat::Ray_Tracing::Raytracer();
		scene = raytracer->get_scene();
		camera = raytracer->get_camera();
		
		set_raytracer(raytracer);
		set_scene(scene);
		
		
		camera_panel->set_camera(camera);
	}
	
}


void Window::open_file(void) {
	QString filename = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath(),tr("Sirat Files (*.sirat)"));
	
	if (!filename.isEmpty()) {
		QFile qfile(filename);
		if(!qfile.open(QIODevice::ReadOnly)) {
			QMessageBox::information(this, tr("Sirat"),tr("Cannot load %1.").arg(filename));
			return;
		}
		else {
			if(file) close_file();
			current_filename = filename;
			input.parse(filename.toStdString().c_str());
			raytracer = input.get_raytracer();
			camera = input.get_camera();
			scene = input.get_scene();
			if(raytracer != 0 && camera != 0 && scene != 0) {
				set_raytracer(raytracer);
				set_scene(scene);
				camera_panel->set_camera(camera);
				
				file = true;
				statusbar->showMessage(tr("%1 opened").arg(filename));
			} else {
				QMessageBox::information(this, tr("Sirat"),tr("Cannot load %1.").arg(filename));
				return;
			}
		}
	}
}

void Window::save_file(void) {
	if(file) {
		input.save(raytracer,scene,camera,current_filename.toStdString().c_str());
		statusBar()->showMessage(tr("%1 saved").arg(current_filename));
	}
}

void Window::save_as_file(void) {
	if(file) {
		QString filename = QFileDialog::getSaveFileName(this,tr("Save As"), QDir::currentPath(),tr("Sirat Files (*.sirat)"));
		if(!filename.endsWith(tr(".sirat")))
			filename += ".sirat";
		input.save(raytracer,scene,camera,filename.toStdString().c_str());
		statusBar()->showMessage(tr("%1 saved").arg(filename));
	}
}
void Window::close_file(void) {
	if(file) {
		if(raytracer != 0) { 
			delete raytracer;
			raytracer = 0;
		}
		scene_tree->clear();
		material_panel->clear();
		file = false;
		current_filename.clear();
	}
}

void Window::render(void) {
	if(file && render_thread == 0) {
		if(outputfile)
			delete outputfile;
		outputfile = new Qtimage(camera->get_grid()->get_width(),camera->get_grid()->get_height());
		
		
		raytracer_rendering = new Sirat::Ray_Tracing::Raytracer(*raytracer);
		
		/**
		 * Temporary hack
		 */
		
		raytracer_rendering->set_output(outputfile);
		render_thread = new Render_Thread(raytracer_rendering,raytracer_panel->get_photontracer(),raytracer_panel->get_sample_photons());
		
		connect(render_thread,SIGNAL(finished()),this,SLOT(render_end()));
		render_timer.start();
			
		progress_timer = new QTimer(this);
		connect(progress_timer, SIGNAL(timeout()), this, SLOT(update_progress()));
		progress_timer->start(100);
		render_thread->start();
	}
	else {
		QMessageBox::information(this, tr("Sirat"),tr("Cannot render."));
	}
}

void Window::stop_render(void) {
	if(render_thread != 0) {
		unsigned int rendering_time;
		rendering_time = render_timer.elapsed();
		const double rendering_time_msecs = (double (rendering_time))/1000.0;
		statusbar->showMessage(tr("Stoped after %1 seconds.").arg(rendering_time_msecs));
		
		// Getting output from rendering process.
		outputfile = raytracer_rendering->get_output();
		QImage* qimage = ((Qtimage*) outputfile)->get_QImage();
		
		disconnect(render_thread,SIGNAL(finished()),this,SLOT(render_end()));
		disconnect(progress_timer, SIGNAL(timeout()), this, SLOT(update_progress()));
		progress_timer->stop();
		progressbar->setValue(0);
		
		// Stopping and deleting render process
		
		render_thread->stop();
		render_thread->wait();
		render_thread->quit();
		
		delete progress_timer;
		progress_timer = 0;
		delete render_thread;
		render_thread = 0;
		delete raytracer_rendering;
		raytracer_rendering = 0;
		
		emit image_rendered(qimage);
	}
}

void Window::render_end(void) {
	unsigned int rendering_time;
	rendering_time = render_timer.elapsed();
	const double rendering_time_msecs = (double (rendering_time))/1000.0;
	bool parallel = false;
	#ifdef _OPENMP
	parallel = raytracer->get_parallel();
	#endif
	if(parallel) { 
		unsigned int num_threads = 1;
		#ifdef _OPENMP
		num_threads = raytracer->get_threads();
		#endif
		statusbar->showMessage(tr("%1 seconds to render (parallel).").arg(rendering_time_msecs,num_threads));
	}
	else statusbar->showMessage(tr("%1 seconds to render (serial).").arg(rendering_time_msecs));
	
	// Getting output from rendering process.
	outputfile = raytracer_rendering->get_output();
	QImage* qimage = ((Qtimage*) outputfile)->get_QImage();
	
	disconnect(render_thread,SIGNAL(finished()),this,SLOT(render_end()));
	disconnect(progress_timer, SIGNAL(timeout()), this, SLOT(update_progress()));
	progress_timer->stop();
	progressbar->setValue(100);
	
	// Deleting all render process
	delete progress_timer;
	progress_timer = 0;
	delete render_thread;
	render_thread = 0;
	delete raytracer_rendering;
	raytracer_rendering = 0;
	
	emit image_rendered(qimage);
	progressbar->setValue(0);
}

void Window::update_progress(void) {
	double progress = raytracer_rendering->get_progress();
	// Updating progress value
	progressbar->setValue(int(progress*100.0));
}


void Window::picking(int _x,int _y) {
	if(camera != 0 && raytracer != 0 && scene != 0) {
		Sirat::Math::Ray picking = camera->generate_ray(_x,camera->get_grid()->get_height() - _y);
		Sirat::Object::Object* object = scene->nearest_object(picking).get<0>();
		if(object != 0) emit material_selected(object->get_material());
		else return;
	}
}

void Window::init(void) {
	showMaximized();
	setCentralWidget(main_widget);
	setWindowTitle(tr("Sirat v0.1"));
	statusBar()->showMessage(tr("Ready"));
}

void Window::create_connections(void) {
	// Menu actions
	connect(actionExit, SIGNAL(triggered()), this, SLOT(close()));
	connect(actionNew, SIGNAL(triggered()), this, SLOT(new_file()));
	connect(actionOpen, SIGNAL(triggered()), this, SLOT(open_file()));
	connect(actionClose,SIGNAL(triggered()), this,SLOT(close_file()));
	connect(actionSave,SIGNAL(triggered()), this,SLOT(save_file()));
	connect(actionSaveas, SIGNAL(triggered()), this, SLOT(save_as_file()));
	
	connect(this,SIGNAL(set_clear()),main_widget,SLOT(clear()));
	connect(render_button, SIGNAL(clicked()), this, SLOT(render()));
	connect(stop_button, SIGNAL(clicked()), this, SLOT(stop_render()));
	connect(this,SIGNAL(image_rendered(QImage*)),main_widget,SLOT(show_image(QImage*)));
	connect(main_widget->get_image(),SIGNAL(clicked(int,int)),this,SLOT(picking(int,int)));
	
	connect(this,SIGNAL(material_selected(Sirat::Material::Material*)),material_panel,SLOT(select_material(Sirat::Material::Material*)));
	/* workaround */
	connect(scene_tree,SIGNAL(object_selected(Sirat::Object::Object*)),material_panel,SLOT(select_object(Sirat::Object::Object*)));
	/* end */
	connect(scene_tree,SIGNAL(material_selected(Sirat::Material::Material*)),material_panel,SLOT(select_material(Sirat::Material::Material*)));
	
// 	connect(this,SIGNAL(material_selected(Sirat::Material::Material*)),scene_panel,SLOT(select_material(Sirat::Material::Material*)));
// 	connect(scene_tree,SIGNAL(material_selected(Sirat::Material::Material*)),scene_panel,SLOT(select_material(Sirat::Material::Material*)));
}

void Window::create_toolbar(void) {
	render_button = new QPushButton(tr("Render"));
	stop_button = new QPushButton(tr("Stop"));
	progressbar = new QProgressBar(this);
	progressbar->setRange(0,100);
	
	toolbar = new QToolBar(tr("Toolbar"),this);
	toolbar->addWidget(render_button);
	toolbar->addWidget(stop_button);
	toolbar->addWidget(progressbar);
	addToolBar(Qt::TopToolBarArea,toolbar);
}

void Window::create_dock_widgets(void) {
	scene_tree = new Scene_Tree();
	dock_scene_tree = new QDockWidget(tr("Scene Tree"),this);
	dock_scene_tree->setWidget(scene_tree);
	addDockWidget(Qt::LeftDockWidgetArea,dock_scene_tree);
	
	scene_panel = new Scene_Panel(this,scene_tree,scene);
	dock_scene_panel = new QDockWidget(tr("Scene Panel"),this);
	dock_scene_panel->setWidget(scene_panel);
	addDockWidget(Qt::LeftDockWidgetArea,dock_scene_panel);
	
	raytracer_panel = new Raytracer_Panel(this,raytracer);
	dock_raytracer_panel = new QDockWidget(tr("Raytracer Panel"),this);
	dock_raytracer_panel->setWidget(raytracer_panel);
	addDockWidget(Qt::LeftDockWidgetArea,dock_raytracer_panel);
	
	material_panel = new Material_Panel(this);
	dock_material_panel = new QDockWidget(tr("Material Panel"),this);
	dock_material_panel->setWidget(material_panel);
	
	
	object_panel = new Object_Panel(material_panel,this);
	dock_object_panel = new QDockWidget(tr("Object Panel"),this);
	dock_object_panel->setWidget(object_panel);
	addDockWidget(Qt::RightDockWidgetArea,dock_object_panel);
	
	addDockWidget(Qt::RightDockWidgetArea,dock_material_panel);
		
	camera_panel = new Camera_Panel(this,camera);
	dock_camera_panel = new QDockWidget(tr("Camera Panel"),this);
	dock_camera_panel->setWidget(camera_panel);
	addDockWidget(Qt::RightDockWidgetArea,dock_camera_panel);
}

void Window::create_menu(void) {
	actionNew = new QAction(this);
	actionOpen = new QAction(this);
	actionClose = new QAction(this);
	actionExit = new QAction(this);
	actionSave = new QAction(this);
	actionSaveas = new QAction(this);
	actionScene_Tree = dock_scene_tree->toggleViewAction();
	actionScene_Panel = dock_scene_panel->toggleViewAction();
	actionObject_Panel = dock_object_panel->toggleViewAction();
	actionMaterial_Panel = dock_material_panel->toggleViewAction();
	actionRaytracer_Panel = dock_raytracer_panel->toggleViewAction();
	actionCamera_Panel = dock_camera_panel->toggleViewAction();
		
	menubar = new QMenuBar();
	menuFile = new QMenu();
	menuTools = new QMenu();
	
	setMenuBar(menubar);	
	menubar->addAction(menuFile->menuAction());
	menubar->addAction(menuTools->menuAction());
	
	menuFile->addAction(actionNew);
	menuFile->addAction(actionOpen);
	menuFile->addAction(actionSave);
	menuFile->addAction(actionSaveas);
	menuFile->addSeparator();
	menuFile->addAction(actionClose);
	menuFile->addSeparator();
	menuFile->addAction(actionExit);

	menuTools->addAction(actionScene_Tree);
	menuTools->addAction(actionScene_Panel);
	menuTools->addAction(actionObject_Panel);
	menuTools->addAction(actionMaterial_Panel);
	menuTools->addAction(actionRaytracer_Panel);
	menuTools->addAction(actionCamera_Panel);
		
	actionNew->setText(tr("New"));
	actionNew->setToolTip(tr("Create a scene"));
	actionNew->setStatusTip(tr("Create a scene."));
	actionNew->setShortcut(tr("Ctrl+N"));
	actionOpen->setText(tr("Open"));
	actionOpen->setToolTip(tr("Open a scene"));
	actionOpen->setStatusTip(tr("Open a scene."));
	actionOpen->setShortcut(tr("Ctrl+O"));
	actionClose->setText(tr("Close"));
	actionClose->setStatusTip(tr("Close"));
	actionClose->setShortcut(tr("Ctrl+C"));
	actionExit->setText(tr("Exit"));
	actionExit->setStatusTip(tr("Exit"));
	actionSave->setText(tr("Save"));
	actionSave->setStatusTip(tr("Save"));
	actionSave->setShortcut(tr("Ctrl+S"));
	actionSaveas->setText(tr("Save as"));
	actionSaveas->setStatusTip(tr("Save as"));
	actionScene_Tree->setText(tr("Scene Tree"));
	actionScene_Tree->setStatusTip(tr("Scene Tree"));
	actionScene_Tree->setShortcut(tr("Ctrl+T"));
		
	menuFile->setTitle(tr("File"));
	menuTools->setTitle(tr("Tools"));
}

void Window::set_raytracer(Sirat::Ray_Tracing::Raytracer* _raytracer)  {
	raytracer = _raytracer;
	raytracer_panel->set_raytracer(_raytracer);
}

void Window::set_scene(Sirat::Common::Scene* _scene)  {
	scene = _scene;
	scene_tree->insert_scene(scene);
	scene_panel->set_scene(scene);
}

}


